---
slug: redux #路径，不能重复
title: redux基础 # 标题
authors: [] # 作者，在authors.yml配置
tags: [facebook, hello, docusaurus] # 标签，在tags.yml配置
toc_min_heading_level: 2 # 最小展示标题
toc_max_heading_level: 5 # 最大展示标题
---
:::warning
**太复杂,推荐`react-redux`+`redux-toolkit`** [redux-toolkit](redux-toolkit)
:::

## redux(状态管理方案)
> redux一般配合`react-redux`，也是实现组件通信的技术

#### 重要性
> 实战开发中，组件间的通信大多都依赖于`ref`/`props`/`redux`

### 操作

#### 创建全局公共容器,用来存储各组件需要的公共信息
```jsx
const store = createStore([reducer]);
```
在创建的store容器中，存储两部分内容
* 公共状态:各组件需要共享或通信的信息
* 事件池: 存放一些方法(让组件可以更新的方法)
---
#### 特点：
* 当公共状态一旦发生改变，会默认立即通知事件池中的方法执行
---

### redux基本操作步骤
1. 注册reducer(管理员)`const store = createStore(reducer)`后得到store
    * store是个函数,两个参数;第一个是state,就是**公共状态**;第二个是action,行为对象,必须要有个type让我们知道到底这个行为要干什么
2. 将得到的`store`给到根组件,使用全局上下文给到下面的所有子组件
3. 在对应的子组件得到`store`
    * 对于函数式组件是`useContext(ThemeContext)`
    * 对于类组件是`static contextType = ThemeContext`;在render函数里使用`const {store} = this.context`
4. 定义组件更新函数(这个更新函数可能跟业务一点关系都没有，只是能让组件更新的方法)
    * 对于函数式组件是`useState`
    * 对于类组件是`this.forceUpdate()`
5. 在组件第一次挂载时候将这个更新函数给到`store.subscribe()`
6. 在业务操作要改动公共状态的时候使用`store.dispatch()`,这个函数内部的是第一步定义reducer时候给到的行为对象里面的`type`,必须完全一致

### 安装
#### redux(原生redux使用)
```shell
npm install redux
```
#### redux-logger
```shell
npm install redux-logger
```
#### redux-promise
```shell
npm install redux-promise
```
#### redux-thunk

```shell
npm install redux-thunk
```

#### react-redux

```shell
npm install react-redux
```


### 定义管理员及得到store
`src/store/index.js`

```js
import {createStore} from 'redux';


const init = {
    up:0,
    down:0
}

/**
 * 管理员：修改STORE容器中的公共状态
 * @param state 存储STORE容器中的公共状态【最开始没有的时候赋值初始值】
 * @param action 每一次基于dispatch派发时候，传递进来的行为对象【要求必须具备type属性(派发的行为标识)】
 * @return state return的内容会整体替换STORE容器中的内容
 */
const reducer = function reducer(state = init, action) {
    switch (action.type) {
        case "VOTE_UP":
            return {...state, up: state.up + 1};
        case "VOTE_DOWN":
            //展开运算符浅克隆,使得引用发生变化,也符合react的不可变性原则
            return {...state, down: state.down+1    };
    }
    return state;
}

const store = createStore(reducer);

export default store;
```

**必须把state返回回去!否则使用的时候getState()返回的是undefined**

---
#### 为什么需要`{...state}?`
> react内部是**浅比较**，比较的是引用，如果直接`state.up++`的话react监听不到变更

* 展开运算符是浅克隆，会得到一个新的对象,这样react内部的`Object.is()`判断的就不等了,这样既让react知道了对象的变更,也符合react的不可变性原则
---

目前已经创建完store了,可以使用了

---
### 如何使用store呢?

> 在应用入口js处使用上下文对象将创建的store放在上下文中,因为所有的组件都可以理解为index.js的后代组件,基于上下文可以拿到store

#### 先导入上下文对象`ThemeContext`
`/src/ThemeContext.js`
```js
import React from "react";

const ThemeContext =React.createContext();

export default ThemeContext;
```

---



#### 将store通过全局上下文的方式分发给下方的组件
`/src/index.js`

```jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import {App} from './views/App';
import ThemeContext from "./ThemeContext";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <>
      <ThemeContext.Provider value={{store:store}}>
          <App/>
      </ThemeContext.Provider>
  </>
)

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

```
### 下面的组件使用store

`src/views/study/redux/ReduxStudy.jsx`

```jsx
import React, {useContext,useEffect,useState} from 'react';
import {Button, Divider} from "antd";
import ThemeContext from "../../ThemeContext";

export const ReduxStudy = function () {
    //解构出context
    const {store} = useContext(ThemeContext);
    console.log(store,'context');
    const {up, down} = store.getState();

    //只是为了让组件更新
    const [_, setState] = useState(0)

    const updateState = ()=>{
        setState(prevState => prevState + 1);
    }

    useEffect(()=>{
        const unSubscribe =  store.subscribe(updateState);
        return ()=>unSubscribe();
    },[])
    return (
        <>
            <div className={'container'}>
                <h2>React是很棒的框架</h2>
                <div>
                    {up+down}
                </div>
            </div>
            <div>
                <div>
                    赞同票数:{up}
                </div>
                <div>
                    反对票数:{down}
                </div>
            </div>
            <Divider/>
            <Button onClick={()=>store.dispatch({type:'VOTE_UP'})}>
                投赞同票
            </Button>

            <Button onClick={()=>store.dispatch({type:'VOTE_DOWN'})}>
                投反对票
            </Button>
        </>
    )

};
```
---

### 解读这段代码
**从现在开始让我们来解读一下，因为redux的原生使用到处都是坑，到处都会让人觉得是原始人操作**
#### 解构全局上下文的store

```jsx
const {store} = useContext(ThemeContext);
```

#### 得到全局状态

```jsx
const {up,down} = store.getState();
```

#### 定义一个能让组件更新的函数

> 这个函数可能跟业务完全不沾边，但是只要是能让组件更新就ok
```jsx
const [_,setState] = useState(1);

const updateState = ()=>{
    setState(prevState=>prevState+1);
}
```

#### 在组件第一次挂载时候把这个能让组件更新的函数给到redux的subscribe函数，这样状态值变更的话redux就会回调这个函数让组件更新了

> subscribe函数会返回一个函数，这个函数能让subscribe订阅的函数取消订阅

* **必须要这个清除函数,因为redux是全局事件，组件取消挂载时候也不会去除掉订阅，一旦状态值被改变，redux会再次触发订阅事件就会报错**
* **而且一堆订阅事件不清除的话会严重影响性能的**
```jsx
useEffect(()=>{
        const unSubscribe =  store.subscribe(updateState);
        return ()=>unSubscribe();
    },[])
```

#### 在组件中传递修改状态值的行为对象

> dispatch传递的必须有个type且必须跟`src/store/index.js`里面的`action.type`完全一致

```jsx
<Button onClick={()=>store.dispatch({type:'VOTE_UP'})}>
   投赞同票
</Button>
```
